package com.vhall.component.service.redpacket.service.impl;

import com.alibaba.fastjson.JSON;
import com.vhall.component.service.redpacket.common.GiveType;
import com.vhall.component.service.redpacket.common.RedpacketConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 红包切片算法
 *
 * @author xuyongzheng
 * @since 2021/3/7 16:28
 **/
@Slf4j
@Component
public class RedPacketGenerator {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 表情最小红包额度(分)
     */
    @Value("${vhall.red-packet.gif-min-money:100}")
    private Integer giftMinMoney;

    @Value("${vhall.red-packet.pack-min-money:1}")
    private Integer packMinMoney;

    @Value("${vhall.red-packet.max_money:10000000}")
    private Integer maxMoney;

    /**
     * 随机金额
     *
     * @param id    编号
     * @param type  类型
     * @param money 金额(分)
     * @param count 数量
     * @return
     */
    public boolean randomSlice(Long id, Integer type, Integer money, Integer count) {
        int min = packMinMoney;
        try {
            String queueName = RedpacketConstants.QUEUE_RED_PACKET_PREFIX + id;

            if (type == GiveType.GIFT.getCode()) {
                //表情类型,最小金额 1元(100分)
                min = giftMinMoney;
            }

            // 金额不足判断
            if (min * count > money) {
                log.error("红包:[{}],总金额不足已按照最小金额分配:总金额:{},每份最小金额:{}, 总份数:{}", id, money, min, count);
                return false;
            }
            List<Integer> result = randomSplit(money, count, min);
            log.info("红包:[{}],随机红包,拆分结果:{}", id, result);

            Collections.sort(result);
            // 对数组进行排序, 由大到小
            Collections.reverse(result);
            log.info("红包:[{}],随机红包,倒序结果:{}", id, result);

            List<String> qmsg = new ArrayList<>();

            int rank = 0;
            int percent = 100;
            for (Integer item : result) {
                //  分转化为元
                Double yuan = (double) item / 100;
                rank = rank + 1;
                if (count > 1) {
                    // percent标识的是用户抢到了当前金额,超过了多少用户,percent起名这里词不达意
                    Double p = ((double) count - rank) / count * 100;
                    percent = p.intValue();
                }
                List<Object> arr = new ArrayList<>();
                // 金额
                arr.add(yuan);
                // 排名
                arr.add(rank);
                // 占比
                arr.add(percent);
                qmsg.add(JSON.toJSONString(arr));

            }
            log.info("红包:[{}],随机红包,计算结果:{}", id, qmsg);

            Collections.shuffle(qmsg);
            log.info("红包:[{}],随机红包,乱序结果:{}", id, qmsg);
            for (String item : qmsg) {
                redisTemplate.opsForList().rightPush(queueName, item);
            }
        } catch (Exception ex) {
            log.error("随机红包分配失败, 红包:[" + id + "],总金额:" + money + ",每份最小金额:" + min + ", 总份数:" + count, ex);
            return false;
        }
        return true;
    }

    /**
     * @param id
     * @param type
     * @param money 单位(分)
     * @param count
     */
    public boolean averageSlice(Long id, Integer type, Integer money, Integer count) {
        int min = packMinMoney;
        try {

            if (type == GiveType.GIFT.getCode()) {
                //表情类型,最小金额 1元(100分)
                min = giftMinMoney;
            }

            // 金额不足判断
            if (min * count > money) {
                log.error("红包:[{}],总金额不足已按照最小金额分配:总金额:{},每份最小金额:{}, 总份数:{}", id, money, min, count);
                return false;
            }
            // ssc:queue:rp:child:xxx
            String queueName = RedpacketConstants.QUEUE_RED_PACKET_PREFIX + id;

            List<Integer> result;
            if (type == GiveType.GIFT.getCode()) {
                //如果是发礼品，则先减掉余数，然后再相除，必须保证结果是整数
                int zs = money - (money % count);
                result = avgSplit(zs, count);
            } else {
                result = avgSplit(money, count);
            }

            log.info("红包:[{}],平均拆分结果:{}", id, result);

            for (Integer item : result) {
                //  分转化为元
                Double yuan = (double) item / 100;
                List<Object> arr = new ArrayList<>();
                arr.add(yuan);
                arr.add(0);
                arr.add(100);
                log.info("队列结果:{}->{}", queueName, JSON.toJSONString(arr));
                redisTemplate.opsForList().rightPush(queueName, JSON.toJSONString(arr));
            }
        } catch (Exception ex) {
            log.error("平均红包分配失败, 红包:[" + id + "],总金额:" + money + ",每份最小金额:" + min + ", 总份数:" + count, ex);
            return false;
        }
        return true;
    }


    private List<Integer> randomSplit(int money, int count, int min) {
        if (money < count) {
            count = money - 3;
        }
        if (!isRight(money, count, min)) {
            return Collections.emptyList();
        }
        List<Integer> list = new ArrayList<>();
        // 红包最大金额为平均金额的TIMES倍
        // 每个红包最大是平均值的倍数
        double times = 2.1;
        int max = (int) (money * times / count);
        max = Math.min(max, maxMoney);
        for (int i = 0; i < count; i++) {
            int one = random(money, min, max, count - i, min);
            list.add(one);
            money -= one;
        }
        return list;

    }

    private int random(int money, int minS, int maxS, int count, int min) {
        // 红包数量为1，直接返回金额
        if (count == 1) {
            return money;
        }
        // 如果最大金额和最小金额相等，直接返回金额
        if (minS == maxS) {
            return minS;
        }
        int max = Math.min(maxS, money);
        // 随机产生一个红包
        int one = ((int) Math.rint(Math.random() * (max - minS) + minS)) % max + 1;
        int money1 = money - one;
        // 判断该种分配方案是否正确
        if (isRight(money1, count - 1, min)) {
            return one;
        } else {
            int avg = money1 / (count - 1);
            if (avg < min) {
                // 递归调用，修改红包最大金额
                return random(money, minS, one, count, min);
            } else if (avg > maxMoney) {
                // 递归调用，修改红包最小金额
                return random(money, one, maxS, count, min);
            }
        }
        return one;
    }

    private boolean isRight(int money, int count, int min) {
        int avg = money / count;
        if (avg < min) {
            return false;
        }
        return avg <= maxMoney;
    }


    /**
     * 均分红包, 不足均分,添加到最后一个
     *
     * @param money 金额(分)
     * @param count 个数
     * @return 金额列表
     */
    private List<Integer> avgSplit(int money, int count) {
        if (money < count) {
            count = money;
        }
        List<Integer> list = new ArrayList<>();
        // 红包最大金额为平均金额的TIMES倍
        int age = money / count;
        for (int i = 0; i < count; i++) {
            if (i + 1 == count) {
                if (age * count == money) {
                    list.add(age);
                } else {
                    int lastMoney = money - age * count;
                    list.add(age + lastMoney);
                }
            } else {
                list.add(age);
            }
        }
        return list;

    }


}
